using System;
using System.Collections.Generic;
using System.Text;
using SlimDX;
using SlimDX.DirectInput;

using System.Windows.Forms;
namespace Barrage.Global
{
    /// <summary>
    /// Manages mouse initialization and access to its information
    /// </summary>
    public class Mouse
    {
		Vector2 maxLimit;
		Vector2 minLimit;
        int absPosX = 0, absPosY = 0;
		Device<MouseState> mouse;
		int currentPosX=0,currentPosY=0;
        int wheelPos = 0;
        int previousWheelPos = 0;
        float wheelGranularity = 120.0f;
        MouseState currentStatus;
		bool []previousButtons=null;
		static Mouse instance=null;
		/// <summary>
		/// Max mouse coordinates. It usually equals the window size (Also it's its default value).
		/// </summary>
		public Vector2 MaxLimit{get{return maxLimit;}set{maxLimit=value;}}
		/// <summary>
		/// Min mouse coordinates. It usually equals the empty vector.
		/// </summary>
		public Vector2 MinLimit{get{return minLimit;}set{minLimit=value;}}

		/// <summary>
		/// Windows Forms Control that holds the directinput device. You should not modify this attribute.
		/// </summary>
		public static Control control=null;
		/// <summary>
		/// Sets both min and max limit for the mouse coordinates.
		/// </summary>
		/// <param name="minLimit"></param>
		/// <param name="maxLimit"></param>
		public void SetLimits(Vector2 minLimit,Vector2 maxLimit)
		{
			MinLimit=minLimit;
			MaxLimit=maxLimit;
		}
		/// <summary>
		/// Returns the mouse instance or creates it
		/// </summary>
		/// <returns>The mouse instance</returns>
		public static Mouse Instance{
			get
			{
				if (instance==null)
					return new Mouse();
				else return instance;
		
			}
		}

		Mouse()
		{						
			if (instance!=null) throw new Exception("A Mouse object is already created");
			instance=this;

            mouse = new Device<MouseState>(SystemGuid.Mouse);
			SetLimits(Vector2.Zero,new Vector2(Engine.Instance.WindowWidth,Engine.Instance.WindowHeight));
			//minLimit=new Vector2(int.MinValue/2,int.MinValue/2);
			//maxLimit=new Vector2(int.MaxValue/2,int.MaxValue/2);
            minLimit = - new Vector2(Engine.DefaultRenderTargetDescription.Width/2f, Engine.DefaultRenderTargetDescription.Height/2f);
            maxLimit = -minLimit;
			
			mouse.SetCooperativeLevel(control,CooperativeLevel.Foreground | CooperativeLevel.Exclusive);			

            mouse.Acquire();

            Engine.RegisterDisposeHandler(new EngineDisposeHandler(Dispose));
		}
		/// <summary>
		/// Returns if the first button was just released
		/// </summary>
		/// <returns>True if the first button has been released</returns>
		public bool OneClick()
		{
			return OneClick(0);
		}
		/// <summary>
		/// Returns if the specified button was just released
		/// </summary>
		/// <param name="button">The mouse button</param>
		/// <returns>True if it was released</returns>
		public bool OneClick(int button)
		{
			if (previousButtons==null) return false;
			return ((previousButtons[(int)button])&&(!(currentStatus.GetButtons()[(int)button])));			
		}

        /// <summary>
        /// Returns if the first mouse button was just released
        /// </summary>
        /// <returns>True if it was just released</returns>
        public bool ButtonReleased()
        {
            return ButtonReleased(0);
        }
        /// <summary>
		/// Returns if the first mouse button was just pressed
		/// </summary>
		/// <returns>True if it was just pressed</returns>
		public bool ButtonPressed()
		{
			return ButtonPressed(0);
		}
        /// <summary>
        /// Returns if the specified mouse button was just released
        /// </summary>
        /// <param name="button">The button number</param>
        /// <returns>True if it was just released</returns>
        public bool ButtonReleased(int button)
        {
            if (previousButtons == null) return false;
            return (((previousButtons[(int)button])) && (!currentStatus.GetButtons()[(int)button]));
        }
        /// <summary>
		/// Returns if the specified mouse button was just pressed
		/// </summary>
		/// <param name="button">The button number</param>
		/// <returns>True if it was just pressed</returns>
		public bool ButtonPressed(int button)
		{
			if (previousButtons==null) return false;
			return ((!(previousButtons[(int)button]))&&(currentStatus.GetButtons()[(int)button]));
		}
		/// <summary>
		/// Returns the first mouse button's current state
		/// </summary>
		/// <returns>True if it's pressed</returns>
		public bool ButtonState()
		{
			return ButtonState(0);
		}
		/// <summary>
		/// Returns the specified mouse button's current state
		/// </summary>
		/// <param name="button">The button number</param>
		/// <returns>True if it's pressed</returns>
		public bool ButtonState(int button)
		{
			return currentStatus.GetButtons()[(int)button];
		}
		/// <summary>
		/// Returns the button state in the previous frame
		/// </summary>
		/// <param name="button">The button numbe</param>
		/// <returns>True if it's pressed</returns>
		public bool PreviousButtonState(int button)
		{
			if (previousButtons==null) return false;
			return previousButtons[(int)button];
		}

        /// <summary>
        /// Returns the mouse wheel's absolute position
        /// </summary>
        public float WheelPosition { get { return wheelPos/wheelGranularity; } }

        /// <summary>
        /// Returns the mouse wheel variation since last update
        /// </summary>
        public float WheelVariation { get { return (wheelPos - previousWheelPos) / wheelGranularity; } }

		/// <summary>
		/// The mouse position on screen coordinates (From (0,0) to (Width,Height))
		/// </summary>
		public Vector2 Position
		{
			get
			{
				return new Vector2(currentPosX,currentPosY);				
			}
		}

        /// <summary>
        /// Mouse position in its absolute coordinates
        /// </summary>
        public Vector2 AbsolutePosition
        {
            get
            {
                return new Vector2(absPosX, absPosY);
            }
        }

        /// <summary>
        /// Position variation since last update
        /// </summary>
        public Vector2 Shift
        {
            get
            {
                if (currentStatus!=null)
                    return new Vector2(currentStatus.X,currentStatus.Y);
                return Vector2.Zero;
            }
        }
		/// <summary>
		/// Reads the mouse state and reacquires access to
		/// the mouse if it is lost.
		/// You should not call this method since the Barrage Engine already does it once per frame.
		/// </summary>
		public void UpdateStatus()
		{
			if (previousButtons!=null) 
			{
				currentStatus.GetButtons().CopyTo(previousButtons,0);
			}
			try 
			{
                mouse.Acquire();
				currentStatus=mouse.GetCurrentState();
				if (previousButtons==null)
					previousButtons=new bool[currentStatus.GetButtons().Length];				

				
				currentPosX+=currentStatus.X;
				currentPosY-=currentStatus.Y;
                absPosX += currentStatus.X;
                absPosY += currentStatus.Y;
                if (currentPosX < (int)minLimit.X)
					currentPosX=(int)minLimit.X;
				if (currentPosY<(int)minLimit.Y)
					currentPosY=(int)minLimit.Y;
				if (currentPosX>(int)maxLimit.X)
					currentPosX=(int)maxLimit.X;
				if (currentPosY>(int)maxLimit.Y)
					currentPosY=(int)maxLimit.Y;
                previousWheelPos = wheelPos;
                wheelPos += currentStatus.Z;
			}
			catch (Exception)
			{				
			}
		}
		
        /// <summary>
		/// Frees the object
		/// </summary>
		public void Dispose()
		{
			mouse.Dispose();
			instance=null;
            NLog.LogManager.GetLogger(ToString()).Debug("Disposing"); 
            GC.SuppressFinalize(this);
		}

        /// <summary>
        /// Destructor. Clear resources
        /// </summary>
        ~Mouse()
        {
            Dispose();
        }
	}

}